#include "sbrush_sms.h"
#include "sms.h"
#include "dag.h"

#include <fstream>

#define SMS_MAGIC (767)

using namespace sbrush;
using namespace std;
using namespace dag;

namespace {
	const char* n_sms_frame_log_path = "sbrush_sms_frame_log.txt";
	ofstream n_sms_frame_log;
}

SmsError::SmsError(const string& msg)
:	AcsError(msg)
{
}

SmsError::~SmsError() throw() {
}


LibSmsError::LibSmsError(int err)
:	m_Code(err),
	SmsError(sms_errorString())
{
}

LibSmsError::~LibSmsError() throw() {
}

IncompatibleFrameSpec::IncompatibleFrameSpec()
:	SmsError("incompatible frame spec")
{
}

IncompatibleFrameSpec::~IncompatibleFrameSpec() throw() {
}

int LibSmsError::code() const {
	return m_Code;
}

int Sms::m_RefCount = 0;

Sms::Sms() {
	if (m_RefCount == 0) {
		int err = sms_init();
		if (err != SMS_OK) {
			throw LibSmsError(err);
		}
	}
	m_RefCount++;
}

Sms::~Sms() throw() {
	m_RefCount--;
	if (m_RefCount <= 0) {
		sms_free();
	}
}

void Sms::inflateHeader(SMS_Header*& header, std::istream& binaryStream) {
	int iHeadBSize = 0;
	int iFrameBSize = 0;
	int nFrames = 0;
	int iMagicNumber = 0;
    streampos startPos = binaryStream.tellg();
	
	free(header);
	header = NULL;
	
	/* read magic number */
	binaryStream.read((char*)&iMagicNumber, sizeof(int));
	if (iMagicNumber != SMS_MAGIC) {
		throw CorruptStream();
	}
	
	/* read size of of header */
	binaryStream.read((char*)&iHeadBSize, sizeof(int));
	if (iHeadBSize <= 0) {
		throw CorruptStream();
	}
	
	/* read number of data Frames */
	binaryStream.read((char*)&nFrames, sizeof(int));	
	if (nFrames <= 0) {
		throw CorruptStream();
	}
	
	/* read size of data Frames */
	binaryStream.read((char*)&iFrameBSize, sizeof(int));
	if (iFrameBSize <= 0) {
		throw CorruptStream();
	}
	
	/* allocate memory for header */
	if ((header = (SMS_Header*)malloc(iHeadBSize)) == NULL) {
		throw FailedAlloc();
	}
	
	/* read header */
	binaryStream.seekg(startPos);
	binaryStream.read((char*)header, iHeadBSize);
	
	if (header->nTextCharacters > 0) {
		header->pChTextCharacters = ((char*)header) + sizeof(SMS_Header);
	}
}

void Sms::serializeHeader(SMS_Header& header, 
						  ostream& binaryStream) {
	int iVariableSize = header.nTextCharacters;
	header.iHeadBSize = sizeof(SMS_Header) + iVariableSize;
	
	/* write header */
	binaryStream.write((char*)&header, (size_t)sizeof(SMS_Header));
	if (header.nTextCharacters > 0) {
		binaryStream.write(header.pChTextCharacters, header.nTextCharacters);
	}
}

void Sms::inflateData(const SMS_Header& header, 
					  SMS_Data& data, 
					  std::istream& binaryStream) {
	if (data.sizeData > 0) {
		sms_freeFrame(&data);
	}
	int err = sms_allocFrameH(const_cast<SMS_Header*>(&header), &data);
	if (err != SMS_OK) {
		throw LibSmsError(err);
	}
	binaryStream.read((char*)data.pSmsData, header.iFrameBSize);
}

void Sms::serializeData(const SMS_Header& header,
						const SMS_Data& data,
						ostream& binaryStream) {
	binaryStream.write((char*)data.pSmsData, header.iFrameBSize);
}

void Sms::logFrame(const SMS_Data& data) {
	if (!n_sms_frame_log.is_open()) {
		n_sms_frame_log.open(n_sms_frame_log_path, ios::trunc);
	}
	
	for (int track = 0; track < data.nTracks; track++) {
		n_sms_frame_log << data.pFSinAmp[track] << "\t";
	}
	
	for (int track = 0; track < data.nTracks; track++) {
		n_sms_frame_log << data.pFSinFreq[track] << "\t";
	}
	
	if (data.pFSinPha != NULL) {
		for (int track = 0; track < data.nTracks; track++) {
			n_sms_frame_log << data.pFSinPha[track] << "\t";
		}
	}
	
	n_sms_frame_log << *data.pFStocGain << "\t";
	
	for (int coef = 0; coef < data.nCoeff; coef++) {
		n_sms_frame_log << data.pFStocCoeff[coef] << "\t";
	}
	
	for (int coef = 0; coef < data.nEnvCoeff; coef++) {
		n_sms_frame_log << data.pSpecEnv[coef] << "\t";
	}
	
	n_sms_frame_log << endl;
}

void Sms::initEmptyFrame(SMS_Data& smsData) {
	smsData.pSmsData = NULL;
	smsData.sizeData = 0;
	smsData.pFSinAmp = NULL;
	smsData.pFSinFreq = NULL;
	smsData.pFSinPha = NULL;
	smsData.nTracks = 0;
	smsData.pFStocGain = NULL;
	smsData.nCoeff = 0;
	smsData.pFStocCoeff = NULL;
	smsData.pResPhase = NULL;
	smsData.nEnvCoeff = 0;
	smsData.pSpecEnv = NULL;
}

void Sms::copy(SMS_Data& to, const SMS_Data& from) {
	int keepPhase = 0;
	if (from.pFSinPha != NULL) {
		keepPhase = 1;
	}
	int stochType = SMS_STOC_NONE;
	if (from.pFStocCoeff != NULL) {
		/* TODO: currently libsms has another stochType called SMS_STOC_IFFT.
		 * According to the comments it looks like this is not currently
		 * implemented, so we assume that all are simple SMS_STOC_APPROX, or
		 * SMS_STOC_NONE.  We'll want to figure out a way to support the new
		 * stochType, but libsms makes this a bit difficult.
		 */
		stochType = SMS_STOC_APPROX;
	}
	sms_freeFrame(&to);
	sms_allocFrame(&to, 
				   from.nTracks, 
				   from.nCoeff, 
				   keepPhase, 
				   SMS_STOC_APPROX, 
				   from.nEnvCoeff);
	sms_copyFrame(&to, 
				  const_cast<SMS_Data*>(&from));
}

void Sms::sms2Frame(const SMS_Data& data, Frame& frame) {
	size_t floatSize = 2 * data.nTracks + 1 + data.nCoeff + data.nEnvCoeff;
	frame.resize(FrameSpec(0, 0, floatSize, 0));
	size_t pos = 0;
	if (data.nTracks > 0) {
		memcpy(&frame.floats[pos], 
			   data.pFSinAmp, 
			   data.nTracks * sizeof(float));
		pos += data.nTracks;
		memcpy(&frame.floats[pos], 
			   data.pFSinFreq, 
			   data.nTracks * sizeof(float));
		pos += data.nTracks;
	}
	frame.floats[pos] = *data.pFStocGain;
	pos += 1;
	if (data.nCoeff > 0) {
		memcpy(&frame.floats[pos], 
			   data.pFStocCoeff, 
			   data.nCoeff * sizeof(float));
		pos += data.nCoeff;
	}
	if (data.nEnvCoeff > 0) {
		memcpy(&frame.floats[pos], 
			   data.pSpecEnv, 
			   data.nEnvCoeff * sizeof(float));
		pos += data.nEnvCoeff;
	}
}

void Sms::frame2Sms(const Frame& frame,
					const SMS_Header& header,
					SMS_Data& data) {
	sms_freeFrame(&data);
	sms_allocFrameH(const_cast<SMS_Header*>(&header), &data);
	Sms::frame2Sms(frame, data);
}

void Sms::frame2Sms(const Frame& frame,
					SMS_Data& data) {
	size_t floatSize = 2 * data.nTracks + 1 + data.nCoeff + data.nEnvCoeff;
	if (frame.fSize != floatSize || 
		frame.bSize > 0 ||
		frame.iSize > 0 ||
		frame.dSize > 0) {
		throw IncompatibleFrameSpec();
	}
	
	size_t pos = 0;
	if (data.nTracks > 0) {
		memcpy(data.pFSinAmp, 
			   &frame.floats[pos], 
			   data.nTracks * sizeof(float));
		pos += data.nTracks;
		memcpy(data.pFSinFreq, 
			   &frame.floats[pos], 
			   data.nTracks * sizeof(float));
		pos += data.nTracks;
	}
	*data.pFStocGain = frame.floats[pos];
	pos += 1;
	if (data.nCoeff > 0) {
		memcpy(data.pFStocCoeff, 
			   &frame.floats[pos], 
			   data.nCoeff * sizeof(float));
		pos += data.nCoeff;
	}
	if (data.nEnvCoeff > 0) {
		memcpy(data.pSpecEnv, 
			   &frame.floats[pos], 
			   data.nEnvCoeff * sizeof(float));
		pos += data.nEnvCoeff;
	}	
}